﻿// --------------------------------------------------------------------------------------------------------------------- 
// <copyright file="RuleFunctions.cs" company="Scrum for Team System">
//   None
// </copyright>
// <summary>
//   Defines the RuleFunctionsMap type.
// </summary>
// ---------------------------------------------------------------------------------------------------------------------

namespace ScrumForTeamSystem.EventService.AggregationService.DataObjects
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Microsoft.TeamFoundation.WorkItemTracking.Client;

    /// <summary>
    /// Defines the RuleFunctionsMap type.
    /// </summary>
    internal class RuleFunctions
    {
        /// <summary>
        /// The supported aggregate types.
        /// </summary>
        private enum DataType
        {
            /// <summary>
            /// Emptpy place holder.
            /// </summary>
            None = 0,

            /// <summary>
            /// The integer type.
            /// </summary>
            Integer = 1,

            /// <summary>
            /// The double type.
            /// </summary>
            Double = 2,

            /// <summary>
            /// The date time type.
            /// </summary>
            DateTime = 3
        }
        
        /// <summary>
        /// Gets or sets CurrentValue function.
        /// </summary>
        /// <value>
        /// The current value.
        /// </value>
        public Func<WorkItem, object> CurrentValue
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets NewValue function.
        /// </summary>
        /// <value>
        /// The new value.
        /// </value>
        public Func<IEnumerable<WorkItem>, object> NewValue
        {
            get;
            set;
        }

        /// <summary>
        /// Tries to create functions for the specified rule.
        /// </summary>
        /// <param name="rule">The aggregation rule.</param>
        /// <param name="workItem">The work item.</param>
        /// <param name="functions">The functions.</param>
        /// <returns>
        /// <c>True</c> if the specified parameters are valid; otherwise <c>false</c>.
        /// </returns>
        public static bool TryCreateRuleFunctions(AggregateRule rule, WorkItem workItem, out RuleFunctions functions)
        {
            DataType dataType;
            Func<IEnumerable<WorkItem>, object> aggregator;

            if (TryGetDataType(rule, workItem, out dataType) && 
                TryGetAggretator(rule, dataType, out aggregator))
            {
                functions = new RuleFunctions
                {
                    CurrentValue = w => w.Fields[rule.Field.Target].Value,
                    NewValue = aggregator
                };
            }
            else
            {
                functions = null;
            }

            return functions != null;
        }

        /// <summary>
        /// Tries to get the type of the data.
        /// </summary>
        /// <param name="rule">The aggregate rule.</param>
        /// <param name="targetWorkItem">The target work item.</param>
        /// <param name="dataType">Type of the data.</param>
        /// <returns>
        /// <c>True</c> if the rule and work item are valid; otherwise <c>false</c>.
        /// </returns>
        private static bool TryGetDataType(AggregateRule rule, WorkItem targetWorkItem, out DataType dataType)
        {
            dataType = DataType.None;
            var field = rule.Field.Target;

            // Find the data type.
            if (typeof(int).IsAssignableFrom(targetWorkItem.Fields[field].FieldDefinition.SystemType))
            {
                dataType = DataType.Integer;
            }

            if (typeof(double).IsAssignableFrom(targetWorkItem.Fields[field].FieldDefinition.SystemType))
            {
                dataType = DataType.Double;
            }

            if (typeof(DateTime).IsAssignableFrom(targetWorkItem.Fields[field].FieldDefinition.SystemType))
            {
                dataType = DataType.DateTime;
            }

            return dataType != DataType.None;
        }

        /// <summary>
        /// Tries to get the aggretator.
        /// </summary>
        /// <param name="rule">The aggregate rule.</param>
        /// <param name="dataType">Type of the data value.</param>
        /// <param name="aggregator">The aggregator.</param>
        /// <returns>
        /// <c>True</c> if the aggreation is valid; otherwise <c>false</c>.
        /// </returns>
        private static bool TryGetAggretator(AggregateRule rule, DataType dataType, out Func<IEnumerable<WorkItem>, object> aggregator)
        {
            aggregator = null;

            if (dataType == DataType.None)
            {
                return false;
            }

            var fieldName = rule.Field.Source;

            switch (rule.AggregateType)
            {
                case AggregationType.Sum:
                    if (dataType == DataType.Integer)
                    {
                        aggregator = wis => wis.Sum(GetSelector<int?>(fieldName));
                    }

                    if (dataType == DataType.Double)
                    {
                        aggregator = wis => wis.Sum(GetSelector<double?>(fieldName));
                    }

                    break;
                case AggregationType.Min:
                    aggregator = wis => wis.Min(GetSelector<object>(fieldName));
                    break;
                case AggregationType.Max:
                    aggregator = wis => wis.Max(GetSelector<object>(fieldName));
                    break;
                case AggregationType.Average:
                    if (dataType == DataType.Integer)
                    {
                        aggregator = wis => wis.Average(GetSelector<int?>(fieldName));
                    }

                    if (dataType == DataType.Double)
                    {
                        aggregator = wis => wis.Average(GetSelector<double?>(fieldName));
                    }

                    break;
                default:
                    throw new ArgumentOutOfRangeException("rule");
            }

            return aggregator != null;
        }

        /// <summary>
        /// Gets the selector.
        /// </summary>
        /// <typeparam name="T">The function type.</typeparam>
        /// <param name="fieldName">Name of the field.</param>
        /// <returns>The selector function.</returns>
        private static Func<WorkItem, T> GetSelector<T>(string fieldName)
        {
            return w => (T)w.Fields[fieldName].Value;
        }
    }
}